package uk.org.kano.wifilogin.data;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;

import android.util.Log;

public class APXMLHandler extends DefaultHandler2 {

	// The namespace of the XML file
	private static final String xmlns = "http://wifilogin.kano.org.uk/accesspointsxml";

	// Detect if the appropriate document was found
	private boolean founddoc = false;
	
	// The current configuration
	private WifiConfigDef wificfg = null;
	
	// The current string in the element
	private String elemstring = null;
	
	// The error count
	private int errcount = 0;
	
	// XML parser
	private SAXParserFactory factory = null;
	private SAXParser parser = null;

	/**
	 * Default constructor
	 * @throws APXMLHandlerException
	 */
	public APXMLHandler() throws APXMLHandlerException
	{
		super();
		try {
			factory = SAXParserFactory.newInstance();
			factory.setFeature("http://xml.org/sax/features/validation", true);
			factory.setFeature("http://xml.org/sax/features/namespaces", true);
			factory.setFeature("http://apache.org/xml/features/validation/schema", true);
			factory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
			//parser.setProperty("http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", SchemaUrl);
			parser = factory.newSAXParser();
		} catch (SAXException e) {
			throw new APXMLHandlerException(e);
		} catch (ParserConfigurationException e) {
			throw new APXMLHandlerException(e);
		}
	}
	
	/**
	 * Parse the XML at a location
	 * @param loc The location of the configuration file to parse
	 * @throws InterruptedException 
	 */
	public void parseConfig(URL loc) throws APXMLHandlerException, InterruptedException
	{
		founddoc = false;
		errcount = 0;
		
		try {
			Log.d(this.getClass().getName(), "Starting config download");
			parser.parse(loc.openStream(), this);
			Log.d(this.getClass().getName(), "Config download complete");
			if (Thread.interrupted()) throw new InterruptedException();
		} catch (SAXException e) {
			throw new APXMLHandlerException(e);
		} catch (IOException e) {
			throw new APXMLHandlerException(e);
		}
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		elemstring += (new String(ch, start, length));
	}

	@Override
	public void endDocument() throws SAXException {
		if (false == founddoc) {
			throw new SAXException("Accesspoint configuration not found");
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if ("accesspoints".equals(localName) || "openconfig".equals(localName) ||
			"wepconfig".equals(localName) || "wpapskconfig".equals(localName)) {
			return;
		} else if ("accesspoint".equals(localName)) {
			if (0 == errcount) {
				// TODO store the wifi configuration
			}
			errcount = 0;
			return;
		} else if (localName.startsWith("loginproperty-")) {
			wificfg.addProperty(localName.substring(localName.indexOf('-')+1), elemstring);
			return;
		} else if ("PSK".equals(localName)) {
			((WifiConfigDef.WpaPskConfig)(wificfg.getAPConfiguration())).setPSK(elemstring);
			return;
		} else if ("key".equals(localName)) {
			((WifiConfigDef.WepConfig)(wificfg.getAPConfiguration())).addKey(elemstring);
			return;
		}
		
		// use reflection to pick up and match the remaining attributes
		localName = localName.substring(0, 1).toUpperCase() + localName.substring(1);
		Method m = null;
		try {
			m = wificfg.getClass().getMethod("set"+localName, String.class);
			m.invoke(wificfg, elemstring);
		} catch (SecurityException e) {
			throw new SAXException(e);
		} catch (NoSuchMethodException e) {
			throw new SAXException(e);
		} catch (IllegalArgumentException e) {
			throw new SAXException(e);
		} catch (IllegalAccessException e) {
			throw new SAXException(e);
		} catch (InvocationTargetException e) {
			throw new SAXException(e);
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes atts) throws SAXException {
		if ("accesspoints".equals(localName) && xmlns.equals(uri)) {
			founddoc = true;
			Log.d(this.getClass().getName(), "Config document looks valid");
		}
		
		if (false == founddoc) return;
		if ("accesspoint".equals(localName)) {
			wificfg = new WifiConfigDef();
		} else if ("openconfig".equals(localName)) {
			wificfg.setAPConfiguration(wificfg.new OpenConfig());
		} else if ("wepconfig".equals(localName)) {
			wificfg.setAPConfiguration(wificfg.new WepConfig());
		} else if ("wpapskconfig".equals(localName)) {
			wificfg.setAPConfiguration(wificfg.new WpaPskConfig());
		}
		elemstring = "";
	}

	@Override
	public void error(SAXParseException exception) throws SAXException {
		Log.e(this.getClass().getName(), exception.toString());
		errcount++;
	}

	@Override
	public void fatalError(SAXParseException exception) throws SAXException {
		Log.e(this.getClass().getName(), exception.toString());
		throw new SAXException(exception);
	}

	@Override
	public void warning(SAXParseException exception) throws SAXException {
		Log.e(this.getClass().getName(), exception.toString());
		errcount++;
	}

	/**
	 * Exception handling
	 * @author timh
	 *
	 */
	public class APXMLHandlerException extends Exception
	{
		public APXMLHandlerException() {
			super();
		}

		public APXMLHandlerException(String detailMessage, Throwable throwable) {
			super(detailMessage, throwable);
		}

		public APXMLHandlerException(String detailMessage) {
			super(detailMessage);
		}

		public APXMLHandlerException(Throwable throwable) {
			super(throwable);
		}

		private static final long serialVersionUID = 1L;
	}
}